/*
 * Copyright 2004-2007 Gary Bentley 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may 
 * not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *    http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.sourceprojects.cappadocia.org.josql.internal;

import java.util.List;
import java.util.ArrayList;

import org.sourceprojects.cappadocia.org.josql.Query;
import org.sourceprojects.cappadocia.org.josql.QueryExecutionException;
import org.sourceprojects.cappadocia.org.josql.QueryParseException;
import org.sourceprojects.cappadocia.org.josql.expressions.ValueExpression;

public class Limit {

	private ValueExpression start = null;
	private ValueExpression rowsCount = null;

	public Limit() {

	}

	public void init(Query q) throws QueryParseException {

		// Init the value expressions...
		if (this.start != null) {

			this.start.init(q);

			// Should probably check to ensure that accessors aren't used...
			// since
			// we don't have a "current object" to work on!.
			Class c = this.start.getExpectedReturnType(q);

			if (!Utilities.isNumber(c)) {

				throw new QueryParseException(
						"The expected return type of the start expression: \""
								+ this.start
								+ "\" of the LIMIT clause is: "
								+ c.getName()
								+ " however the expression when evaluated must return a numeric result.");

			}

		}

		this.rowsCount.init(q);

		// Should probably check to ensure that accessors aren't used... since
		// we don't have a "current object" to work on!.

		// Try and determine what the expected type is that we will return.
		Class c = this.rowsCount.getExpectedReturnType(q);

		if (!Utilities.isNumber(c)) {

			throw new QueryParseException(
					"The expected return type of the rows count expression: \""
							+ this.rowsCount
							+ "\" of the LIMIT clause is: "
							+ c.getName()
							+ " however the expression when evaluated must return a numeric result.");

		}

	}

	public List getSubList(List objs, Query q) throws QueryExecutionException {

		// Get the row count.
		Object o = this.rowsCount.evaluate(null, q);

		int rows = -1;

		// Ensure that it is a number.
		if ((o != null) && (!(o instanceof Number))) {

			throw new QueryExecutionException(
					"Return value of rows count expression: \""
							+ this.rowsCount
							+ "\" for the LIMIT clause is of type: "
							+ o.getClass().getName()
							+ " expected it to return a numeric value.");

		}

		if (o != null) {

			// There are rounding issues here, but if the user provides a
			// float/double value...
			rows = ((Number) o).intValue();

		}

		int start = 0;

		// Now get the start value...
		if (this.start != null) {

			Object s = this.start.evaluate(null, q);

			// Ensure that it is a number.
			if ((s != null) && (!(s instanceof Number))) {

				throw new QueryExecutionException(
						"Return value of the start expression: \"" + this.start
								+ "\" for the LIMIT clause is of type: "
								+ s.getClass().getName()
								+ " expected it to return a numeric value.");

			}

			if (s != null) {

				// There are rounding issues here, but if the user provides a
				// float/double value...
				start = ((Number) s).intValue();

				// Whilst for the user rows start at 1, for us they start at
				// 0...
				start--;

			}

		}

		int ls = objs.size();

		// Now get our sub-list.
		if (start > (ls - 1)) {

			// Return nothing, outside of the range.
			return new ArrayList();

		}

		if (rows > 0) {

			if ((start + rows) > (ls - 1)) {

				// Just return the rows starting at start...
				// We return a new list to prevent issues with modifications...
				return new ArrayList(objs.subList(start, ls));

			}

			// Here we return start + rows.
			return new ArrayList(objs.subList(start, start + rows));

		} else {

			// Just ignore the rows...
			return new ArrayList(objs.subList(start, ls));

		}

	}

	public void setStart(ValueExpression v) {

		this.start = v;

	}

	public void setRowsCount(ValueExpression v) {

		this.rowsCount = v;

	}

}
